#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;

#define MakeRand() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234) // 获取随机数
#define C_NUM 5 //创建子进程个数
typedef void (*func_t)(); // 函数指针类型

void downLoadTask() /////////////////////////////////////////////我们自己模拟出来的任务
{
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}
void ioTask()
{
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}
void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}
void LoadTaskFunc(std::vector<func_t> *out)
{
    assert(out); // 判断数组不为空，也就是任务表不能为空
    out->push_back(downLoadTask);
    out->push_back(ioTask);
    out->push_back(flushTask);
} ///////////////////////////////////////////////////////////////

class SubEp // 我们将这个结构体放到数组里面，那么父进程通过任意管道对进程的操作就变成了对数组下标对应的操作
{
public:
    SubEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
        char my_buffer[1024];
        snprintf(my_buffer, sizeof(my_buffer), "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_); // 查看num的值，子进程pid和文件描述符
        name_ = my_buffer;
    }

public: // 这里为了方便正文代码部分的调用，就把成员变量设置为public的
    static int num;
    std::string name_; // 进程的名字
    pid_t subId_;      // 子进程pid
    int writeFd_;      // 管道的写端，很重要，父进程靠写端给子进程发消息
};
int SubEp::num = 0;
// 这里定义num是因为我们创建子进程的时候，子进程的id值是波动的，导致我们这里为进程取名字的时候每次都不一样，所以加个num，每次初始化为0，然后++保证创建的name是可以一样的

int recvtask(int readfd)
{
    int code = 0;
    int n = read(readfd, &code, sizeof(code));
    if (n == sizeof(int))
        return code;
    else if (n <= 0)
        return -1;
    else
        return 0;
}

void SendTask(const SubEp &my_id, int fm_num) // 将任务发送给进程
{
    cout << "send task num: " << fm_num << " send to -> " << my_id.name_ << endl;
    int num = write(my_id.writeFd_, &fm_num, sizeof(fm_num));
    assert(num == sizeof(int));
    (void)num;
}

void creatSubPoints(std::vector<SubEp> *subs, std::vector<func_t> &funcMap)
{
    std::vector<int> deleteFd;///////////////////////////////////////////////这个数组存放着上一个子进程的写端
    for (int i = 0; i < C_NUM; i++)
    {
        int fds[2];        // 获取文件描述符，fds[0]是读端，fds[1]是写端
        int n = pipe(fds); // 创建匿名管道
        assert(n == 0);
        (void)n;           // 这里是因为debug模式下assert生效，但是在release模式下assert失效了。这个时候n没有被使用就报警告，变量没有被使用，这里强转是表示使用了变量，避免警告
        pid_t id = fork(); // 创建子进程
        if (id == 0)
        {
            for(int i = 0; i < deleteFd.size(); i++) close(deleteFd[i]);//////////////////////////清除上一个子进程的读端
            // 子进程, 进行处理任务
            close(fds[1]); // 子进程关闭写端
            while (true)   // 子进程一直做下面的事情
            {
                // 1、子进程要获取命令码，如果没有发送，应该阻塞
                int my_code = recvtask(fds[0]); // 从读端获取命令码
                // 2、完成任务
                if (my_code >= 0 && my_code < funcMap.size())
                    funcMap[my_code]();
                else if (my_code == -1 || my_code == 0) // 其实这里不可能等于0
                    break;
                // else
                // cout << "my_code error!" << endl;
            }
            exit(0);
        }
        close(fds[0]);
        SubEp sub(id, fds[1]); // 这里创建对象
        subs->push_back(sub);  // 将创建好的对象尾插到数组里面，方便正文进行操作
        deleteFd.push_back(fds[1]);/////////////////////////////////////////////////////////将该进程的写端存放到数组里面
    }
}

void MyidLoadBash(const std::vector<SubEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    int Id_num = subs.size(); // 子进程个数
    int fm_num = funcMap.size();
    bool quit = (count == 0) ? true : false;
    while (true)
    {
        // 1、选择一个进程——> std::vector<SubEp> ——> index下标
        int subidx = rand() % Id_num;
        // 2、选择一个任务——> std::vector<func_t> ——> index下标
        int fmidx = rand() % fm_num;
        // 3、将任务发给进程
        SendTask(subs[subidx], fmidx); // 将任务fmidx发送给数组中的具体进程
        sleep(1);
        if (!quit)
        {
            --count;
            if (count == 0)
                break;
        }
    }
    // write quit -> read 0
    for (int i = 0; i < Id_num; i++)
        close(subs[i].writeFd_); // 关一个文件描述符就直接进行waitpid();
}

void waitMyid(std::vector<SubEp> Myid)
{
    int num = Myid.size();
    for (int i = 0; i < num; ++i)
    {
        waitpid(Myid[i].subId_, nullptr, 0);
        cout << "wait sub process success ...: " << Myid[i].subId_ << endl;
    }
}

int main()
{
    MakeRand(); // 生成随机数
    // 1. 建立子进程并建立和子进程通信的信道, 这里是有bug的，但是不影响我们后面编写，我们代码写完了然后讲这个bug
    // 1.1加载方发表
    std::vector<func_t> funcMap; // 创建一个表格，表格里面存放着我们要执行的任务
    LoadTaskFunc(&funcMap);
    // 1.2 创建子进程，并且维护好父子通信信道
    std::vector<SubEp> subs;        // 这样父进程通过管道对子进程发送消息就简化成为了对数组下标的处理
    creatSubPoints(&subs, funcMap); // 将创建管道和进程封装为一个函数接口，然后把任务表也加载过去

    // 2. 走到这里就是父进程, 控制子进程
    int Count = 3; // 0: 永远进行
    MyidLoadBash(subs, funcMap, Count);

    // 3. 回收子进程信息
    waitMyid(subs);
    return 0;
}